package fr.alma.coeur;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.InvalidPropertiesFormatException;
import java.util.Iterator;
import java.util.Properties;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Classe de gestion des plugins.
 * @author AUBERT Sebastien
 * @author BOUVET Frederic
 * @author BRAUD Jeremy
 * @author KROMMENHOEK Cedric
 *
 */
public class GestionPlugins {

	/**
	 * Activateur des plugins charge de maniere paresseuse.
	 */	
	private Activateur activateur;
	
	/**
	 * Ensemble des descripteurs des plugins.
	 */
	private ArrayList<Properties> descripteurs;
	/**
	 * Ensembles des plugins d'entree actives.
	 */
	private ArrayList<String> pluginsEntreeActives;
	/**
	 * Le loggeur.
	 */
	private Loggeur loggeur;
	
	/**
	 * Constructeur.
	 */
	public GestionPlugins() {
		this.activateur = new Activateur();
		this.descripteurs = new ArrayList<Properties>();
		this.pluginsEntreeActives = new ArrayList<String>();
		this.loggeur = Loggeur.getLoggeur();
	}
	
	/**
	 * Chargement des plugins du dossier par defaut.
	 */
	public void chargerDossierPlugins() {
		this.loggeur.log("GestionPlugin: Parcours du repertoire des plugins...");
		File repertoire = new File("./plugins");
		File[] fichiers = repertoire.listFiles(new FilenameFilter(){
			  public boolean accept(File dir, String name) {
				    return name.endsWith(".xml");
				  }
		});
		for (int i = 0; i < fichiers.length; i++) {
			File fichier = fichiers[i];
			try {
				Properties p = new Properties();
				FileInputStream in = new FileInputStream(fichier.getAbsoluteFile());
				p.loadFromXML(in);
				String nomPlugin = p.getProperty("package") + "." + p.getProperty("nom");
				this.loggeur.log("GestionPlugin: Ajout du plugin " + nomPlugin);
				this.ajoutProprietes(p);
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} catch (InvalidPropertiesFormatException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		this.loggeur.log("GestionPlugin: Fin du parcours.");
	}
	
	/**
	 * Methode de chargement des plugins contenus dans une archive .jar.
	 * @param cheminFichier le chemin de l'archive
	 */
	public void chargerJarPlugin(String cheminFichier) {
		File fichier = new File(cheminFichier);
		try {
			URI u = fichier.toURI();
			URLClassLoader loader = new URLClassLoader(new URL[]{u.toURL()}); 
			JarFile jar = new JarFile(fichier);
			Enumeration<JarEntry> entreesJar = jar.entries();
			while (entreesJar.hasMoreElements()) {
				String entree = entreesJar.nextElement().toString();
				if (entree.endsWith(".class")) {
					entree = entree.substring(0, entree.length() - 6);
					entree = entree.replaceAll("/", ".");
					
					Properties p = this.activateur.ajoutPluginExterne(entree, loader);
					this.ajoutProprietes(p);
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Methode d'ajout de proprietes a la liste des descripteurs.
	 * @param p les proprietes a ajouter
	 */
	public void ajoutProprietes(Properties p) {
		if (p.size() == 5) {
			this.descripteurs.add(p);
		}
	}
	
	/**
	 * Methode de calcul du nombre de plugins geres.
	 * @return le nombre de plugins
	 */
	public int nombrePlugins() {
		return this.descripteurs.size();
	}
	
	/**
	 * Methode d'acces aux noms des plugins charges.
	 * @return la liste des noms
	 */
	public ArrayList<String> listePlugins() {
		ArrayList<String> listeNoms = new ArrayList<String>();
		for (Iterator<Properties> iterator = this.descripteurs.iterator(); iterator.hasNext();) {
			Properties p = iterator.next();
			listeNoms.add(p.getProperty("nom"));
		}
		return listeNoms;
	}
	
	/**
	 * Methode d'acces aux noms des plugins d'entree.
	 * @return la liste des noms
	 */
	public ArrayList<String> listePluginsEntree() {
		ArrayList<String> listeNoms = new ArrayList<String>();
		for (Iterator<Properties> iterator = this.descripteurs.iterator(); iterator.hasNext();) {
			Properties p = iterator.next();
			if (p.getProperty("type").equals("entree")) {
				listeNoms.add(p.getProperty("nom"));
			}
		}
		return listeNoms;
	}
	
	/**
	 * Methode d'acces aux noms des plugins intermediaire.
	 * @param pluginEntree le nom du plugin d'entree
	 * @return la liste des noms
	 */
	public ArrayList<String> listePluginsTransformateur(String pluginEntree) {
		Properties pEntree = this.getPropertiesPlugin(pluginEntree);
		ArrayList<String> listeNoms = new ArrayList<String>();
		for (Iterator<Properties> iterator = this.descripteurs.iterator(); iterator.hasNext();) {
			Properties p = iterator.next();
			if (p.getProperty("type").equals("transformateur") 
					&& p.getProperty("categorie").equals(pEntree.getProperty("categorie"))) {
				listeNoms.add(p.getProperty("nom"));
			}
		}
		return listeNoms;
	}
	
	/**
	 * Methode d'acces aux noms des plugins de sortie.
	 * @param pluginEntree le nom du plugin d'entree
	 * @return la liste des noms
	 */
	public ArrayList<String> listePluginsSortie(String pluginEntree) {
		Properties pEntree = this.getPropertiesPlugin(pluginEntree);
		ArrayList<String> listeNoms = new ArrayList<String>();
		for (Iterator<Properties> iterator = this.descripteurs.iterator(); iterator.hasNext();) {
			Properties p = iterator.next();
			if (p.getProperty("type").equals("sortie") 
					&& p.getProperty("categorie").equals(pEntree.getProperty("categorie"))) {
				listeNoms.add(p.getProperty("nom"));
			}
		}
		return listeNoms;
	}
	
	/**
	 * Methode de verification de l'existence du plugin.
	 * @param nomPlugin le nom du plugin
	 * @return vrai si le plugin existe
	 */
	public boolean existePlugin(String nomPlugin) {
		return this.listePlugins().contains(nomPlugin);
	}
	
	/**
	 * Methode d'acces aux proprietes d'un plugin.
	 * @param nomPlugin le nom du plugin
	 * @return les proprietes du plugin
	 */
	private Properties getPropertiesPlugin(String nomPlugin) {
		for (Properties p : this.descripteurs) {
			if (p.getProperty("nom").equals(nomPlugin)) {
				return p;
			}
		}
		return null;
	}
	
	/**
	 * Methode d'acces au nom du package d'un plugin.
	 * @param nomPlugin le nom du plugin
	 * @return le nom du package du plugin
	 */
	public String getPackage(String nomPlugin) {
		if (this.existePlugin(nomPlugin)) {
			return this.getPropertiesPlugin(nomPlugin).getProperty("package");
		} else {
			return null;
		}
	}
	
	/**
	 * Methode d'acces a la description d'un plugin.
	 * @param nomPlugin le nom du plugin
	 * @return la description du plugin
	 */
	public String getDescription(String nomPlugin) {
		if (this.existePlugin(nomPlugin)) {
			return this.getPropertiesPlugin(nomPlugin).getProperty("description");
		} else {
			return null;
		}
	}
	
	/**
	 * Methode d'acces a la categorie d'un plugin.
	 * @param nomPlugin le nom du plugin
	 * @return la categorie du plugin
	 */
	public String getCategorie(String nomPlugin) {
		if (this.existePlugin(nomPlugin)) {
			return this.getPropertiesPlugin(nomPlugin).getProperty("categorie");
		} else {
			return null;
		}
	}
	
	/**
	 * Ajout d'un lien entre deux plugins.
	 * @param nomPluginEntree le nom du plugin d'entree
	 * @param nomPluginSortie le nom du plugin de sortie
	 */
	public void ajoutLien(String nomPluginEntree, String nomPluginSortie) {
		this.activateur.ajoutLien(nomPluginEntree, nomPluginSortie);
	}
	
	/**
	 * Suppression d'un lien entre deux plugins.
	 * @param nomPluginEntree le nom du plugin d'entree
	 * @param nomPluginSortie le nom du plugin de sortie
	 */
	public void suppressionLien(String nomPluginEntree, String nomPluginSortie) {
		this.activateur.suppressionLien(nomPluginEntree, nomPluginSortie);
	}
	
	/**
	 * Methode de calcul du nombre de liens.
	 * @return le nombre de liens
	 */
	public int nombreLiens() {
		return this.activateur.nombreLiens();
	}
		
	/**
	 * Ajout de liens entre un plugin d'entree et une liste de plugins.
	 * Si la liste contient des plugins intermediaires, ils doivent etre ajoute avant.
	 * @param nomPluginEntree le nom du plugin d'entree
	 * @param listePlugins la liste des plugins de sortie
	 */
	public void ajoutLiens(String nomPluginEntree, ArrayList<String> listePlugins) {
		String pluginEntree = this.getPackage(nomPluginEntree) + "." + nomPluginEntree;
		for (String plugin : listePlugins) {
			String nomPlugin = this.getPackage(plugin) + "." + plugin;
			this.activateur.ajoutLien(pluginEntree, nomPlugin);
			if (this.getCategorie(plugin).equals("transformateur")) {
				pluginEntree = nomPlugin;
			}
		}
	}
	
	/**
	 * Ajout d'un plugin selectionne.
	 * @param nomPluginEntree le nom du plugin
	 */
	public void ajoutPluginSelectionne(String nomPluginEntree) {
		this.pluginsEntreeActives.add(nomPluginEntree);
	}
	
	/**
	 * Suppression d'un plugin selectionne.
	 * @param nomPluginEntree le nom du plugin
	 */
	public void suppressionPluginSelectionne(String nomPluginEntree) {
		for (String plugin : this.pluginsEntreeActives) {
			if (plugin.equals(nomPluginEntree)) {
				this.pluginsEntreeActives.remove(plugin);
				break;
			}
		}
	}
	
	/**
	 * Methode de calcul du nombre de plugins d'entree selectionnes.
	 * @return le nombre de plugins d'entree selectionnes
	 */
	public int nombrePluginsSelectionnes() {
		return this.pluginsEntreeActives.size();
	}
	
	/**
	 * Teste si le plugin d'entree est selectionne.
	 * @param pluginEntree le nom du plugin d'entree
	 * @return vrai si le plugin est deja selectionne
	 */
	public boolean estPluginEntreeSelectionne(String pluginEntree) {
		for (String plugin : this.pluginsEntreeActives) {
			if (plugin.equals(pluginEntree)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Enregistre la liste des plugins selectionnes.
	 * @param pluginsSelectionnes la liste des plugins selectiones
	 */
	public void setPluginsSelectionnes(ArrayList<String> pluginsSelectionnes) {
		this.pluginsEntreeActives.clear();
		this.pluginsEntreeActives = pluginsSelectionnes;
	}

	/**
	 * Demarre les plugins d'entree selectionnes.
	 */
	public void demarrerPlugins() {
		for (String plugin : this.pluginsEntreeActives) {
			String nomPlugin = this.getPackage(plugin) + "." + plugin;
			this.loggeur.log("GestionPlugin: Demarrage du plugin d'entree " + nomPlugin);
			this.activateur.activerPlugin(nomPlugin);
		}
	}
	
	/**
	 * Arrete les plugins d'entree selectionnes.
	 */
	public void arreterPlugins() {
		for (String plugin : this.pluginsEntreeActives) {
			String nomPlugin = this.getPackage(plugin) + "." + plugin;
			this.loggeur.log("GestionPlugin: Arret du plugin d'entree " + nomPlugin);
			this.activateur.arreterPlugin(nomPlugin);
		}
	}
	
}
